home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / MTASK20.ZIP / MTASK.DOC < prev    next >
Text File  |  1990-03-12  |  18KB  |  526 lines

  1. .dhMTASK 2.0 by Wayne Conrad
  2. .np Documentation written using Multi-Edit 4.01Pd
  3. .rm 72
  4. .df .ce -.pa-
  5. .tm6
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12. .ceMTASK
  13.  
  14.  
  15. MTASK is a unit for Turbo Pascal 5.5, to allow a Turbo Pascal program
  16. to exhibit simple multi-tasking.  MTASK gives your program a
  17. non-preemptive, request driven multi-tasking capability.  I will
  18. explain what I mean by that later.
  19.  
  20. MTASK 2.0 was written and donated to the public domain by Wayne E.
  21. Conrad (me) in March of 1990.  I may be contact via my BBS,
  22.  
  23.      Pascalaholics Anonymous
  24.      (602) 484-9356
  25.      300/1200/2400 bps
  26.      24 hours/day
  27.  
  28. or by mail at my home:
  29.  
  30.      10 E Bell Road #1001
  31.      Phoenix, AZ  85022
  32.  
  33. I am interested in any modifications, bug reports, or comments you
  34. have.
  35.  
  36. If you modify this package, please keep my name and the name of any
  37. other programmers who've worked on it intact.  Give credit to yourself,
  38. too!  Please distribute the complete package, with documentation and
  39. demonstration programs included.
  40.  
  41.  
  42. 1.1 INTRODUCTION
  43.  
  44.  
  45. MTASK allows your Turbo Pascal program to do simple multi-tasking.  I
  46. call MTASK's brand of multi-tasking "non-preemptive, request driven."
  47.  
  48. Preemptive means that the switch from one task to another can happen at
  49. almost any time.  Most preemptive systems have an interrupt driver
  50. hooked to a hardware timer, which causes a task switch every time the
  51. hardware timer goes off.  The advantage of this kind of multi-tasking
  52. is that your programs don't have to be written with multi-tasking in
  53. mind, and don't even have to know that its taking place.  Also, no
  54. program can hog the system for long, because the interrupt driver
  55. switches from one program to another fairly often.  Desqview and
  56. Double-DOS, and OS/2 are operating environments that do preemptive,
  57. interrupt-driven multi-tasking.  The disadvantage of this kind of
  58. multi-tasking is that it can be complex to write and difficult in the
  59. extreme to debug.  These difficulties are compounded in an MS-DOS
  60. environment because MS-DOS was not meant to be used in a multi-tasking
  61. environment.
  62.  
  63. On the other hand, non-preemptive multi-tasking only switches tasks at
  64. certain, well defined times.  There is no interrupt driver that forces
  65. task switches.  In the original Macintosh operating system, for example,
  66. task switches only occured when a task called the operating system.  The
  67. advantage of non-preemptive multi-tasking is that it is much simpler to
  68. write and debug than preemptive multi-tasking, because the system has
  69. total control of when task-switches occur.  The disadvantage to this
  70. form of multi-tasking is that a task must request a task switch often if
  71. the other tasks are to receive their chance to execute.  If a task does
  72. not request a task switch for a long time, the other tasks will appear
  73. to pause.  What's worse, if a task crashes, it won't be able to call the
  74. operating system to let the other tasks execute, so they'll all be hung
  75. too.
  76.  
  77. MTASK implements a very simple method of non-preemptive multi-tasking
  78. that I call "request driven."  Request driven means that task switches
  79. occur only when the current task calls MTASK and requests a switch.
  80. (The sole exception is that a task switch occurs when the current task
  81. terminates itself).  This is about the simplest form of multi-tasking
  82. that I can envision.  It is so simple that the entire MTASK unit
  83. compiles to only about 1400 bytes with stack checking and range
  84. checking turned off, or less if you don't use all of its procedures.
  85. This simplicity also made MTASK easy to write and debug.  MTASK was
  86. written in one day!
  87.  
  88.  
  89.  
  90. 1.2 WHAT ARE MTASK'S LIMITS?
  91.  
  92.  
  93. MTASK allows your program to set up multiple tasks within itself.
  94. These tasks will execute concurrently.  However, it does not effect
  95. anything outside of your program.  It does not allow you to run
  96. multiple programs, multiple copies of COMMAND.COM, or anything else
  97. like that.  It simply allows your program to do several things
  98. concurrently without stumbling over itself.
  99.  
  100. As far as DOS is concerned, your program using MTASK is still just a
  101. simple program.  All of the gymnastics to keep track of multiple tasks
  102. are done by MTASK, withing your program, without the knowledge or
  103. consent of DOS or anything else outside of your program.  Because MTASK
  104. is so simple, it will coexist fine with any "real" multi-tasking DOS
  105. you have set up, such as DesqView or Double-DOS.  Whenever the DOS
  106. gives your program some time, your program will dole out that time to
  107. its tasks.
  108.  
  109. Your program must continue to execute for its tasks to execute.  If any
  110. task in your program exits to DOS for any reason, including a run-time
  111. error, all tasks stop executing.  If one of your tasks shells out by
  112. using Turbo's Exec function, then the other tasks in your program are
  113. suspended until control returns from the Exec function to your program.
  114.  
  115. MTASK must not be made into an overlay.  Any of the tasks it controls
  116. may be overlays, although that may be unwise.  You could end up loading
  117. an overlay from disk during each task switch!
  118.  
  119.  
  120. 2.1 SUMMARY OF PROCEDURES AND FUNCTIONS
  121.  
  122.  
  123. To use MTASK, include it in your program's USES statements.  MTASK will
  124. initialize itself automatically, making your main program task #1.
  125. Your program can then use the following procedures and functions to
  126. create and control tasks:
  127.  
  128.  
  129.      create_task         Create another task
  130.  
  131.      terminate_task      Destroy a task
  132.  
  133.     switch_task        Switch to another task
  134.  
  135.     current_task_id    Return the task ID of the current task
  136.  
  137.     number_of_tasks    Return the current number of tasks
  138.  
  139.  
  140. 2.1.1 PROCEDURE CREATE_TASK
  141.  
  142.  
  143. PROCEDURE create_task
  144.   (
  145.   task      : task_proc;
  146.   VAR param ;
  147.   stack_size: Word;
  148.   VAR id    : Word;
  149.   VAR result: Word
  150.   );
  151.  
  152.  
  153.      TASK is the procedure to make into a task.  It must match type
  154.      task_proc, having a single variable as its parameter.
  155.  
  156.      PARAM is the parameter to pass to new_task.  It may be a variable
  157.      of any type, so long its what the task expects.  For example, if
  158.      you pass a Word and the task expects a LongInt, the task will get
  159.      invalid data.
  160.  
  161.      STACK_SIZE is the size of the new task's stack.  A stack will be
  162.      allocated from the heap.  The minimum stack size is 512 bytes, but
  163.      most tasks will need more.
  164.  
  165.      ID is set to the task ID of the newly created task.  If the task
  166.      is not created because of an error, then id is not set.
  167.  
  168.     RESULT is the result code, which is set to one of these values:
  169.  
  170.         0                No error, task created ok
  171.  
  172.           heap_full           Unable to allocate heap for the task's
  173.                               stack
  174.  
  175.           too_many_tasks      Maximum number of tasks are already
  176.                               running
  177.  
  178.  
  179. The new task is created and added to the end of the task list.  The new
  180. task will be executed when the task before it calls switch_task.
  181.  
  182.  
  183. 2.1.2 PROCEDURE TERMINATE_TASK
  184.  
  185.  
  186. PROCEDURE terminate_task (id: Word; VAR result: Word);
  187.  
  188.  
  189.      ID is the task id of the task you want to terminate.  If ID = 0,
  190.      then the current task will be terminated.
  191.  
  192.     RESULT is the result code, which is set to one of these values.
  193.  
  194.           0                   No error, task deleted ok
  195.  
  196.         invalid_task_id    There is no task with that ID number
  197.  
  198.  
  199. The designated task will be removed from the task list.  If its stack
  200. was allocated from the heap, it is returned to the heap.
  201.  
  202. If the terminated task is the current task and there is another task in
  203. the task list, a task switch occurs.  On the other hand, if the
  204. terminated task is the current task and there are no other tasks in the
  205. task list, then the program exits to DOS.
  206.  
  207. A task may terminate itself by returning from its main procedure.  For
  208. example, when this task is executed, it will immediately display a
  209. message and then terminate itself.
  210.  
  211.  
  212.      PROCEDURE task (VAR param);
  213.     BEGIN
  214.        Writeln ('We just started, but already we're terminating')
  215.     END;
  216.  
  217.  
  218. 2.1.3 PROCEDURE switch_task
  219.  
  220.  
  221.     PROCEDURE switch_task;
  222.  
  223.  
  224. This procedure causes an immediate switch to the next task in the task
  225. list.  The task list is always scanned as a circular list.  For
  226. example, if there are three tasks in the list -- task 1, task 2, and
  227. task 3 -- then they will be executed in this order:
  228.  
  229.  
  230.     1, 2, 3, 1, 2, 3, 1, 2, 3 . . .
  231.  
  232.  
  233. If the current task is the only task, then no task switch occurs.
  234.  
  235. The stack pointer is switched to its position in the new task's stack.
  236. If the new task has just been created, then its main procedure will be
  237. executed from the beginning.  On the other hand, if the new task had
  238. put itself to sleep by asking for a task switch, then control will
  239. return to the point where it called switch_task.
  240.  
  241.  
  242.  
  243. 2.1.4 FUNCTION CURRENT_TASK_ID
  244.  
  245.  
  246.      FUNCTION current_task_id: Word;
  247.  
  248.  
  249. This function returns the task ID number of the currently executing
  250. task.  When calling an MTASK procedure to do something to a task, the
  251. task ID number is always used to identify the task.
  252.  
  253. A task is assigned its ID number when it is created.  A task's ID
  254. number belongs to it as long as that task exists, and will not be
  255. changed or reassigned until the task terminates.  Even after the task
  256. terminates, Mtask will avoid re-assigning its ID for as long as
  257. possible.  Since there are 65535 possible task ID's, this could be a
  258. very long time indeed.
  259.  
  260.  
  261.  
  262. 2.1.5 FUNCTION NUMBER_OF_TASKS
  263.  
  264.  
  265.      FUNCTION number_of_tasks: Word;
  266.  
  267.  
  268. This function returns the number of tasks in the task list.  There will
  269. always be at least one task.
  270.  
  271.  
  272. 3.1 TRICKS AND TRAPS
  273.  
  274.  
  275. This section focuses on some of the tricks and traps of programming in
  276. this multi-tasking environment.  Like all multi-tasking environments,
  277. strange things can happen.  You'll learn how to watch for problems with
  278. shared data, and crunched parameters.
  279.  
  280. I will only give a few examples of the problems that can occur in
  281. multi-tasking environments.  There are other problems that can occur
  282. when using MTASK, although the problems are less numerous and simpler
  283. to solve than when using a preemptive multi-tasking system.  This
  284. section should help you to begin thinking like a real-time programmer,
  285. giving you an idea of the kinds of problems to watch for.  For a real
  286. education on concurrent programming, head to your library or book store
  287. and look for a book on operating systems.
  288.  
  289.  
  290.  
  291. 3.1.1 PASSING PARAMETERS TO TASKS
  292.  
  293.  
  294. When you create a task, you can pass a parameter to it.  For example, a
  295. BBS program needs to tell a task which node it is, so that the task
  296. knows which serial port to use for i/o.  The parameter you pass is
  297. "untyped," meaning that it can be any type of variable.  You must be
  298. familiar with how Turbo handles untyped variables.
  299.  
  300. The sample program TEST1.PAS shows how to pass a word variable to a
  301. task.  You can pass any kind of variable, including records, arrays,
  302. and even files.
  303.  
  304. One thing to remember is that when you pass an untyped parameter to a
  305. task, you are actually passing the address of the parameter, not the
  306. parameter itself.  Therefore, if you pass the task a paramater and then
  307. modify the parameter, the task may see the new value instead of the old
  308. value.  It will all depend upon where task switches occur.
  309.  
  310. As a general rule, parameters you pass to a task should be global
  311. variables or typed constants.  Global variables and typed constants are
  312. both in the data segment.  Local variables are declared on the current
  313. task's stack, and cannot be assured of existing for very long.  If you
  314. a procedure passes one of its local variables to a task that it's
  315. creating, and then the procedure returns, that local variable is
  316. "thrown away" and its space can be reused by other procedures.  That
  317. would cause the value of the parameter you passed to the task to change
  318. unpredictably.
  319.  
  320.  
  321. 3.1.2 SHARED DATA
  322.  
  323.  
  324. Problems can occur when two or more tasks are using the same global
  325. variables.  If two or more tasks have access to the same variable, you
  326. need to consider carefully what will happen if two tasks access the
  327. variable concurrently.  This pseudo-code example shows two tasks.
  328. task_a is computing the sum of an array of Reals.  Task_b is clearing
  329. the values in the array.
  330.  
  331.  
  332.     CONST
  333.       data_size = 1000;
  334.     VAR
  335.       data: ARRAY [1..data_size] OF Real;
  336.  
  337.  
  338.     PROCEDURE task_a;
  339.        .
  340.        .
  341.        .
  342.     sum := 0.0;
  343.     FOR i := 1 TO data_size DO
  344.       BEGIN
  345.       sum := sum + data [i];
  346.       switch_task;
  347.       END;
  348.        .
  349.        .
  350.        .
  351.     END;
  352.  
  353.  
  354.     PROCEDURE task_b;
  355.        .
  356.        .
  357.        .
  358.     FOR i := data_size DOWNTO 1 DO
  359.       BEGIN
  360.       data [i] := 0.0;
  361.       switch_task;
  362.       END;
  363.        .
  364.        .
  365.        .
  366.     END;
  367.  
  368.  
  369. Do you see what happens if task_a is computing the average at the same
  370. time task_b is clearing the array?  The average will end up being
  371. incorrect, because the data being averaged is being changed while the
  372. average is being computed.  Obviously, this example is contrived.
  373. Nobody in their right mind would call switch_task inside those loops.
  374. That causes many more context switches than are necessary.
  375.  
  376. One way to avoid the problem in this particular example is not to call
  377. switch_task inside either of the loops.  Then you could be sure that an
  378. average would not take place while you were clearing the array, and
  379. array clearing would not take place during an average.
  380.  
  381. You cannot always avoid calling switch_task, however.  Suppose that
  382. floating point addition on your computer was so slow that it took many
  383. seconds to compute the average.  You may have other tasks that cannot
  384. afford to be denied CPU time for more than a fraction of a second.
  385. What do you do?
  386.  
  387. The solution here is to create a flag that indicates when a task is
  388. using the data array.  When one task is using the data array the flag
  389. will be set to True, indicating that no other task should access it.
  390.  
  391.  
  392.     CONST
  393.       flag: Boolean = False;
  394.  
  395.  
  396.     PROCEDURE task_a;
  397.        .
  398.        .
  399.        .
  400.     WHILE flag DO
  401.       switch_task;
  402.     flag := True;
  403.     sum := 0.0;
  404.     FOR i := 1 TO data_size DO
  405.       BEGIN
  406.       sum := sum + data [i];
  407.       switch_task;
  408.       END;
  409.     flag := False;
  410.        .
  411.        .
  412.        .
  413.     END;
  414.  
  415.     PROCEDURE task_b;
  416.        .
  417.        .
  418.        .
  419.     WHILE flag DO
  420.       switch_task;
  421.     flag := True;
  422.     FOR i := data_size DOWNTO 1 DO
  423.       BEGIN
  424.       data [i] := 0.0;
  425.       switch_task;
  426.       END;
  427.     flag := True;
  428.        .
  429.        .
  430.        .
  431.     END;
  432.  
  433.  
  434. Do you see what's going on here?  Before task_a does an average, it
  435. checks the flag to see whether someone else is messing with the data
  436. array.  If someone is, then it waits until the data structure is
  437. available, sets the flag to indicate that it now "owns" the data array,
  438. and proceeds to compute the average.  When the average is finished,
  439. task_a resets the flag, to allow any other task which is waiting for
  440. the data array to have access.  task_b is doing exactly the same thing.
  441.  
  442. Now both tasks can go on calling switch_task even while messing with
  443. the data array, without concern that some other task will access the
  444. data array at the same time.  This technique will work for any number
  445. of tasks.
  446.  
  447.  
  448.  
  449. 3.1.3 WHEN TO SWITCH TASKS?
  450.  
  451.  
  452. Obviously, the examples in 3.1.2 switch tasks far too often.  The
  453. program will spend more time bouncing from one task to another than it
  454. will doing anything useful!  If your loop is too time consuming to
  455. leave out task switches, and switching tasks during every iteration of
  456. the loop is too often, try something like this:
  457.  
  458.  
  459.     FOR i := 1 TO 10000 DO
  460.       BEGIN
  461.       IF i MOD 100 = 0 THEN
  462.          switch_task;
  463.       do_something_useful;
  464.       END;
  465.  
  466.  
  467. This will switch tasks every hundreth iteration of the loop.
  468.  
  469. If your program is going to do something that takes a while, like disk
  470. i/o, it should probably switch tasks before doing so to let the other
  471. tasks get some time before the long delay occurs.  In fact, if you are
  472. doing several lengthy disk operations in a row, call switch_task before
  473. every one.
  474.  
  475.  
  476.     Assign (inf, 'INPUT.DAT');
  477.      switch_task;
  478.     Reset (inf);
  479.     Assign (outf, 'OUTPUT.DAT');
  480.      switch_task;
  481.     Rewrite (outf);
  482.  
  483.  
  484. Many programs have to wait for input at some point.  Input loops are a
  485. perfect place to switch tasks.  In fact, any time a task cannot proceed
  486. because its input is not ready, or for any other reason, it should
  487. switch tasks.
  488.  
  489.  
  490.     WHILE NOT KeyPressed DO
  491.        switch_task;
  492.     ch := ReadKey;
  493.  
  494.  
  495. It is a matter of judgement where task switches should occur.  It will
  496. depend upon the program and circumstances around each operation.
  497.  
  498.  
  499.  
  500. 4.1 REVISION HISTORY
  501.  
  502.  
  503. Version 1.0, MTASK10.ARC.
  504.  
  505.      Original release by Wayne E. Conrad
  506.  
  507. Version 1.1, MTASK11.ARC.
  508.  
  509.      Minor changes to documentation, including using spaces instead of
  510.      tabs.  ARC file now includes the original documentation in
  511.      Multi-Edit format, as well as the printable file.
  512.  
  513. Version 2.0, MTASK20.ZIP.
  514.  
  515.      When a task is terminated, Mtask tries to not assign that task's
  516.      id to a new task for as long as possible.  In order to accomplish
  517.      this, task id's are now words (1..65535).
  518.  
  519.      The create_task procedure was often returning bogus error numbers
  520.      when in fact no error had occured.  Fixed.
  521.  
  522.      The get_task_info procedure is gone, in preparation for a change 
  523.      in the way task information is stored.  If you need this 
  524.      procedure, you should be able to copy it out of version 1.1 
  525.      without any problems.
  526.